/**
 * Class: PMath
 *
 * Purpose: Math for Processing
 *
 * Created: Jan 20, 2010 @ 4:49:46 PM
 * @author Ken Scott
 */
package org.openfantasia.util.math;

import processing.core.PApplet;

import java.text.NumberFormat;
import java.util.List;
import java.util.Random;

public class PMath {

	public static final double G = 6.6726e-11;		// gravitational constant

	public static final double PI = Math.PI;

	public static final double E = Math.E;

	public static final double TWO_PI = 2.0 * PI;

	public static final double HALF_PI = PI / 2.0;

	public static final double SQRT2 = Math.sqrt(2d);

	public static final double SQRT2_2 = SQRT2 / 2d;

	private static Random s_random = new Random(1);


	public static void setRandomSeed(long seed) {
		synchronized (PMath.class) {
			s_random = new Random(seed);
		}
	}


	public static boolean randomBoolean() {
		synchronized (PMath.class) {
			return s_random.nextDouble() < 0.5;
		}
	}


	public static boolean randomProbability(double probability) {
		synchronized (PMath.class) {
			return s_random.nextDouble() < probability;
		}
	}


	public static int randomSign() {
		synchronized (PMath.class) {
			return (s_random.nextDouble() < 0.5) ? -1 : 1;
		}
	}


	public static int random(int range) {
		synchronized (PMath.class) {
			return s_random.nextInt(range);
		}
	}


	public static float random(float range) {
		synchronized (PMath.class) {
			return range * s_random.nextFloat();
		}
	}


	public static double random(double range) {
		synchronized (PMath.class) {
			return range * s_random.nextDouble();
		}
	}


	public static int random(int min, int max) {
		synchronized (PMath.class) {
			return min + s_random.nextInt(max - min);
		}
	}


	public static float random(float min, float max) {
		synchronized (PMath.class) {
			return min + ((max - min) * s_random.nextFloat());
		}
	}


	public static double random(double min, double max) {
		synchronized (PMath.class) {
			return min + ((max - min) * s_random.nextDouble());
		}
	}


	public static Object random(List list) {
		synchronized (PMath.class) {
			return list.get(s_random.nextInt(list.size()));
		}
	}


	public static double avg(double... src) {
		double result = 0f;
		for (int i = 0; i < src.length; i++) {
			result += src[i];
		}
		return result / (double) src.length;
	}


	public static float avg(float... src) {
		float result = 0f;
		for (int i = 0; i < src.length; i++) {
			result += src[i];
		}
		return result / (float) src.length;
	}


	public static double avg(double[]... src) {
		double result = 0f;
		for (int i = 0; i < src.length; i++) {
			result += avg(src[i]);
		}
		return result / (double) src.length;
	}


	public static float avg(float[]... src) {
		float result = 0f;
		for (int i = 0; i < src.length; i++) {
			result += avg(src[i]);
		}
		return result / (float) src.length;
	}


	public static double[] mix(double[]... src) {
		double[] result = new double[src[0].length];
		double r;

		for (int i = 0; i < src[0].length; i++) {
			r = 0;
			for (int j = 0; j < src.length; j++) {
				r += src[j][i];
			}
			result[i] = r / (double) src.length;
		}

		return result;
	}


	public static float[] mix(float[]... src) {
		float[] result = new float[src[0].length];
		float r;

		for (int i = 0; i < src[0].length; i++) {
			r = 0;
			for (int j = 0; j < src.length; j++) {
				r += src[j][i];
			}
			result[i] = r / (float) src.length;
		}

		return result;
	}


	private static void printIndent(int indent) {
		for (int i = 0; i < indent; i++) {
			System.out.print(" ");
		}
	}


	public static void printHeader(int length, int cellWidth, int indent) {
		NumberFormat intFormat = NumberFormat.getIntegerInstance();
		intFormat.setMaximumIntegerDigits(1 + (int) Math.log10(length));
		intFormat.setMinimumIntegerDigits(1 + (int) Math.log10(length));

		printIndent(indent);
		for (int i = 0; i < length; i++) {
			String s = intFormat.format(i);
			System.out.print(s);
			printIndent(cellWidth - s.length());
		}
	}


	public static void print(double[] src, int intDigits, int fracDigits) {
		printHeader(src.length, intDigits + fracDigits + 2, 0);
		System.out.println();

		NumberFormat doubleFormat = NumberFormat.getNumberInstance();
		doubleFormat.setMinimumIntegerDigits(intDigits);
		doubleFormat.setMaximumIntegerDigits(intDigits);
		doubleFormat.setMinimumFractionDigits(fracDigits);
		doubleFormat.setMaximumFractionDigits(fracDigits);

		for (int i = 0; i < src.length; i++) {
			System.out.print(doubleFormat.format(src[i]) + " ");
		}
		System.out.println();
	}


	public static void print(float[] src, int intDigits, int fracDigits) {
		printHeader(src.length, intDigits + fracDigits + 2, 0);
		System.out.println();

		NumberFormat floatFormat = NumberFormat.getNumberInstance();
		floatFormat.setMinimumIntegerDigits(intDigits);
		floatFormat.setMaximumIntegerDigits(intDigits);
		floatFormat.setMinimumFractionDigits(fracDigits);
		floatFormat.setMaximumFractionDigits(fracDigits);

		for (int i = 0; i < src.length; i++) {
			System.out.print(floatFormat.format(src[i]) + " ");
		}
		System.out.println();
	}


	public static void print(double[][] src, int intDigits, int fracDigits) {
		printHeader(src.length, intDigits + fracDigits + 2, 10);
		System.out.println();

		NumberFormat intFormat = NumberFormat.getIntegerInstance();
		intFormat.setMaximumIntegerDigits(9);
		intFormat.setMinimumIntegerDigits(9);

		NumberFormat doubleFormat = NumberFormat.getNumberInstance();
		doubleFormat.setMinimumIntegerDigits(intDigits);
		doubleFormat.setMaximumIntegerDigits(intDigits);
		doubleFormat.setMinimumFractionDigits(fracDigits);
		doubleFormat.setMaximumFractionDigits(fracDigits);

		for (int i = 0; i < src.length; i++) {
			System.out.print(intFormat.format(i) + " ");
			for (int j = 0; j < src[i].length; j++) {
				System.out.print(doubleFormat.format(src[i][j]) + " ");
			}
			System.out.println();
		}
		System.out.println();
	}


	public static void print(float[][] src, int intDigits, int fracDigits) {
		printHeader(src.length, intDigits + fracDigits + 2, 10);
		System.out.println();

		NumberFormat intFormat = NumberFormat.getIntegerInstance();
		intFormat.setMaximumIntegerDigits(9);
		intFormat.setMinimumIntegerDigits(9);

		NumberFormat floatFormat = NumberFormat.getNumberInstance();
		floatFormat.setMinimumIntegerDigits(intDigits);
		floatFormat.setMaximumIntegerDigits(intDigits);
		floatFormat.setMinimumFractionDigits(fracDigits);
		floatFormat.setMaximumFractionDigits(fracDigits);

		for (int i = 0; i < src.length; i++) {
			System.out.print(intFormat.format(i) + " ");
			for (int j = 0; j < src[i].length; j++) {
				System.out.print(floatFormat.format(src[i][j]) + " ");
			}
			System.out.println();
		}
		System.out.println();
	}


	public static boolean isBetween(float time, float min, float max) {
		return (time >= min) && (time < max);
	}


	public static float sign(float v) {
		return (v >= 0) ? 1 : -1;
	}


	public static float frac(float v) {
		return v - (int) v;
	}


	public static float abs(float v) {
		return Math.abs(v);
	}


	public static int abs(int i) {
		return PApplet.abs(i);
	}


	public static float sq(float v) {
		return PApplet.sq(v);
	}


	public static float sqrt(float v) {
		return PApplet.sqrt(v);
	}


	public static float log(float v) {
		return PApplet.log(v);
	}


	public static float exp(float v) {
		return PApplet.exp(v);
	}


	public static float pow(float v, float v1) {
		return PApplet.pow(v, v1);
	}


	public static int max(int i, int i1) {
		return PApplet.max(i, i1);
	}


	public static float clip(float min, float max, float value) {
		return min(max, max(min, value));
	}


	public static float max(float v, float v1) {
		return PApplet.max(v, v1);
	}


	public static int max(int i, int i1, int i2) {
		return PApplet.max(i, i1, i2);
	}


	public static float max(float v, float v1, float v2) {
		return PApplet.max(v, v1, v2);
	}


	public static int max(int[] ints) {
		return PApplet.max(ints);
	}


	public static int min(int i, int i1) {
		return PApplet.min(i, i1);
	}


	public static float min(float v, float v1) {
		return PApplet.min(v, v1);
	}


	public static int min(int i, int i1, int i2) {
		return PApplet.min(i, i1, i2);
	}


	public static float min(float v, float v1, float v2) {
		return PApplet.min(v, v1, v2);
	}


	public static int min(int[] ints) {
		return PApplet.min(ints);
	}


	public static float min(float[] floats) {
		return PApplet.min(floats);
	}


	public static int constrain(int i, int i1, int i2) {
		return PApplet.constrain(i, i1, i2);
	}


	public static float constrain(float v, float v1, float v2) {
		return PApplet.constrain(v, v1, v2);
	}


	public static float sin(float v) {
		return PApplet.sin(v);
	}


	public static float cos(float v) {
		return PApplet.cos(v);
	}


	public static float tan(float v) {
		return PApplet.tan(v);
	}


	public static float asin(float v) {
		return PApplet.asin(v);
	}


	public static float acos(float v) {
		return PApplet.acos(v);
	}


	public static float atan(float v) {
		return PApplet.atan(v);
	}


	public static float atan2(float v, float v1) {
		return PApplet.atan2(v, v1);
	}


	public static float degrees(float v) {
		return PApplet.degrees(v);
	}


	public static float radians(float v) {
		return PApplet.radians(v);
	}


	public static int ceil(float v) {
		return PApplet.ceil(v);
	}


	public static int floor(float v) {
		return PApplet.floor(v);
	}


	public static int round(float v) {
		return PApplet.round(v);
	}


	public static float mag(float v, float v1) {
		return PApplet.mag(v, v1);
	}


	public static float mag(float v, float v1, float v2) {
		return PApplet.mag(v, v1, v2);
	}


	public static float mag(float v, float v1, float v2, float v3) {
		return (float) Math.sqrt(sq(v) + sq(v1) + sq(v3) + sq(v3));
	}


	public static float dist(float v, float v1, float v2, float v3) {
		return PApplet.dist(v, v1, v2, v3);
	}


	public static float dist(float v, float v1, float v2, float v3, float v4, float v5) {
		return PApplet.dist(v, v1, v2, v3, v4, v5);
	}


	public static float lerp(float v, float v1, float v2) {
		return PApplet.lerp(v, v1, v2);
	}


	public static float unlerp(float value, float start, float stop) {
		return (value - start) / (stop - start);
	}


	public static float norm(float v, float v1, float v2) {
		return PApplet.norm(v, v1, v2);
	}


	public static float map(float v, float v1, float v2, float v3, float v4) {
		return PApplet.map(v, v1, v2, v3, v4);
	}


	public static float log(float base, float v) {
		return log(v) / log(base);
	}


	public static float log2(float v) {
		return log(2, v);
	}


	public static float log10(float v) {
		return log(10, v);
	}


	public static boolean isBetween(double time, double min, double max) {
		return (time >= min) && (time <= max);
	}


	public static double sign(double v) {
		return (v >= 0) ? 1 : -1;
	}


	public static double frac(double v) {
		return v - (int) v;
	}


	public static double abs(double v) {
		return Math.abs(v);
	}


	public static double sq(double v) {
		return Math.pow(v, 2);
	}


	public static double sqrt(double v) {
		return Math.sqrt(v);
	}


	public static double log(double v) {
		return Math.log(v);
	}


	public static double exp(double v) {
		return Math.exp(v);
	}


	public static double pow(double v, double v1) {
		return Math.pow(v, v1);
	}


	public static double clip(double min, double max, double value) {
		return min(max, max(min, value));
	}


	public static double max(double v, double v1) {
		return Math.max(v, v1);
	}


	public static double max(double v, double v1, double v2) {
		return Math.max(Math.max(v, v1), v2);
	}


	public static double min(double v, double v1) {
		return Math.min(v, v1);
	}


	public static double min(double v, double v1, double v2) {
		return Math.min(Math.min(v, v1), v2);
	}


	public static double min(double[] doubles) {
		double result = doubles[0];
		for (int i = 1; i < doubles.length; i++) {
			double d = doubles[i];
			if (d < result) {
				result = d;
			}
		}
		return result;
	}


	public static double constrain(double v, double v1, double v2) {
		return min(v1, max(v2, v));
	}


	public static double sin(double v) {
		return Math.sin(v);
	}


	public static double cos(double v) {
		return Math.cos(v);
	}


	public static double tan(double v) {
		return Math.tan(v);
	}


	public static double asin(double v) {
		return Math.asin(v);
	}


	public static double acos(double v) {
		return Math.acos(v);
	}


	public static double atan(double v) {
		return Math.atan(v);
	}


	public static double atan2(double v, double v1) {
		return Math.atan2(v, v1);
	}


	public static double degrees(double radians) {
		return 360d * radians / (2d * Math.PI);
	}


	public static double radians(double degrees) {
		return (2d * Math.PI) * degrees / 360d;
	}


	public static double ceil(double v) {
		return Math.ceil(v);
	}


	public static double floor(double v) {
		return Math.floor(v);
	}


	public static double round(double v) {
		return Math.round(v);
	}


	public static double mag(double a, double b) {
		return Math.sqrt((a * a) + (b * b));
	}


	public static double mag(double a, double b, double c) {
		return Math.sqrt((a * a) + (b * b) + (c * c));
	}


	public static double dist(double x1, double y1, double x2, double y2) {
		return sqrt(sq(x2 - x1) + sq(y2 - y1));
	}


	public static double dist(double x1, double y1, double z1, double x2, double y2, double z2) {
		return sqrt(sq(x2 - x1) + sq(y2 - y1) + sq(z2 - z1));
	}


	public static double lerp(double start, double stop, double amt) {
		return start + (stop - start) * amt;
	}


	public static double unlerp(double value, double start, double stop) {
		return (value - start) / (stop - start);
	}


	public static double norm(double value, double start, double stop) {
		return (value - start) / (stop - start);
	}


	public static double map(double value,
			double istart, double istop,
			double ostart, double ostop) {
		return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
	}


	public static double log(double base, double v) {
		return log(v) / log(base);
	}


	public static double log2(double v) {
		return log(2, v);
	}


	public static double log10(double v) {
		return log(10, v);
	}


	public static double[] crossProduct3(double[] v0, double[] v1) {
		double crossProduct[] = new double[3];

		crossProduct[0] = v0[1] * v1[2] - v0[2] * v1[1];
		crossProduct[1] = v0[2] * v1[0] - v0[0] * v1[2];
		crossProduct[2] = v0[0] * v1[1] - v0[1] * v1[0];

		return crossProduct;
	}


	public static float[] transform3d(float[] a, float[][] m) {
		if ((a.length != 4) || (m.length != 4) || (m[0].length != 4)) {
			throw new IllegalArgumentException("Arguments for 3D transform are float[4] and float[4][4]");
		}

		float a0 = a[0];
		float a1 = a[1];
		float a2 = a[2];
		float a3 = a[3];
		float[] m0 = m[0];
		float[] m1 = m[1];
		float[] m2 = m[2];
		float[] m3 = m[3];

		float[] result = new float[a.length];
		result[0] = (a0 * m0[0]) + (a1 * m0[1]) + (a2 * m0[2]) + (a3 * m0[3]);
		result[1] = (a0 * m1[0]) + (a1 * m1[1]) + (a2 * m1[2]) + (a3 * m1[3]);
		result[2] = (a0 * m2[0]) + (a1 * m2[1]) + (a2 * m2[2]) + (a3 * m2[3]);
		result[3] = (a0 * m3[0]) + (a1 * m3[1]) + (a2 * m3[2]) + (a3 * m3[3]);

		return result;
	}


	public static double[] transform3d(double[] a, double[][] m) {
		if ((a.length != 4) || (m.length != 4) || (m[0].length != 4)) {
			throw new IllegalArgumentException("Arguments for 3D transform are double[4] and double[4][4]");
		}

		double a0 = a[0];
		double a1 = a[1];
		double a2 = a[2];
		double a3 = a[3];
		double[] m0 = m[0];
		double[] m1 = m[1];
		double[] m2 = m[2];
		double[] m3 = m[3];

		double[] result = new double[a.length];
		result[0] = (a0 * m0[0]) + (a1 * m0[1]) + (a2 * m0[2]) + (a3 * m0[3]);
		result[1] = (a0 * m1[0]) + (a1 * m1[1]) + (a2 * m1[2]) + (a3 * m1[3]);
		result[2] = (a0 * m2[0]) + (a1 * m2[1]) + (a2 * m2[2]) + (a3 * m2[3]);
		result[3] = (a0 * m3[0]) + (a1 * m3[1]) + (a2 * m3[2]) + (a3 * m3[3]);

		return result;
	}


	public static float[][] matmul3d(float[][] a, float[][] b) {
		if ((a.length != 4) || (a[0].length != 4) || (b.length != 4) || (b[0].length != 4)) {
			throw new IllegalArgumentException("Arguments for 3D matrix multiply are both float[4][4]");
		}

		float[][] result = Math2D.copyFill(a, 0f);
		for (int i = 0; i < 4; i++) {
			result[i][0] = a[i][0] * b[0][0] + a[i][1] * b[1][0] + a[i][2] * b[2][0] + a[i][3] * b[3][0];
			result[i][1] = a[i][0] * b[0][1] + a[i][1] * b[1][1] + a[i][2] * b[2][1] + a[i][3] * b[3][1];
			result[i][2] = a[i][0] * b[0][2] + a[i][1] * b[1][2] + a[i][2] * b[2][2] + a[i][3] * b[3][2];
			result[i][3] = a[i][0] * b[0][3] + a[i][1] * b[1][3] + a[i][2] * b[2][3] + a[i][3] * b[3][3];
		}
		return result;
	}


	public static double[][] matmul3d(double[][] a, double[][] b) {
		if ((a.length != 4) || (a[0].length != 4) || (b.length != 4) || (b[0].length != 4)) {
			throw new IllegalArgumentException("Arguments for 3D matrix multiply are both double[4][4]");
		}

		double[][] result = Math2D.copyFill(a, 0d);
		for (int i = 0; i < 4; i++) {
			result[i][0] = a[i][0] * b[0][0] + a[i][1] * b[1][0] + a[i][2] * b[2][0] + a[i][3] * b[3][0];
			result[i][1] = a[i][0] * b[0][1] + a[i][1] * b[1][1] + a[i][2] * b[2][1] + a[i][3] * b[3][1];
			result[i][2] = a[i][0] * b[0][2] + a[i][1] * b[1][2] + a[i][2] * b[2][2] + a[i][3] * b[3][2];
			result[i][3] = a[i][0] * b[0][3] + a[i][1] * b[1][3] + a[i][2] * b[2][3] + a[i][3] * b[3][3];
		}
		return result;
	}
}
